
/**
 * closure 闭包
 * 
 * 犀牛书：
 *    函数变量 可以保存在函数作用域内
 * 
 * 高级程序设计： 
 *    闭包是指有全访问另一个 函数作用域中的变量的函数 (函数没导出)
 * 
 * 你不知道的JS：
 *    当函数可以记住并访问所在的词法作用域时，就产生了闭包，即使函数是在当
 * 前词法作用域之外执行
 * 
 * MDN：
 *   一个函数和对其周围状态（lexical environment，词法环境）的引用
 *  捆绑在一起（或者说函数被引用包围），这样的组合就是闭包（closure）
 *  也就是说，闭包让你可以在一个内层函数中访问到其外层函数的作用域
 * 
 * 
 * 总结：闭包
 *    1. 当函数的执行，导致函数被定义 （并抛出——可以不抛）
 *    2. 闭包和函数的定义有关
 * 
 *    this 和 函数的执行方式有关
 * 
 * 优点：
 *    保护函数内的变量安全 ，实现封装，防止变量流入其他环境发生命名冲突
 *    在内存中维持一个变量，可以做缓存（但使用多了同时也是一项缺点，消耗内存）。
 *    匿名自执行函数可以减少内存消耗。（function（）{}）{}
 * 
 * 坏处：
 *    被引用的私有变量不能被销毁，增大了内存消耗，造成内存泄漏，
 * 解决方法是可以在使用完变量后手动为它赋值为null
 *    其次由于闭包涉及跨域访问，所以会导致性能损失，我们可以通过
 * 把跨作用域变量存储在局部变量中，然后直接访问局部变量，来减轻对执行速度的影响
 * 
 * 函数内部包裹函数，作用域链得不到释放，造成消耗内存
 */


// function foo() {
//   var n = 0;
// }
// console.log(n);

// function foo() {
//   var n = 0;
//   function bar() {
//     console.log(n);
//   }
//   bar();
// }

// foo()
// foo()()

// 以下这个不是闭包， 因为fn执行时，fn并没有被定义
// function foo(fn) {
//   var n = 0;
//   fn();
// }
// function test() {
//   console.log(n);
// }
// foo(test);



// 闭包的形式：

// 1. 函数的返回值是函数
// function foo() {
//   var n = 0;
//   return function (){
//     console.log(n);
//   }
// }

// 2. 返回变量是函数
// function foo() {
//   var n = function (){
//     console.log(n);
//   }
//   return n;
// }

// 3. 全局变量定义的闭包
// let outter;
// function foo() {
//   let n = 0;
//   outter = function (){
//     console.log(n);
//   }
//   return n;
// }
// foo();
// outter();

// 4. 函数的参数的方式
// let inner = function (fn) {
//   console.log(fn());
// }
// function foo() {
//   let str = 'local';
//   let n = function (){
//     return str;
//   }
//   inner(n);
// }
// foo();
// 或
// ;(function () {
//   let str = 'local';
//   let n = function (){
//     return str;
//   }
//   inner(n);
// })();

// 5. 循环赋值问题
// function foo() {
//   let arr = [];
//   for (var i = 0; i < 10; i++) {
//     arr[i] = (function (i) {
//       return function() {
//         console.log(i);
//       };
//     })(i);
//   }
//   return arr;
// }

// const bar = foo();
// bar[0]();

// 6. 迭代器、累加器

// let add = (() => {
//   let count = 0;
//   return function () {
//     return ++ count;
//   }
// })();

// console.log(add());
// console.log(add());
// console.log(add());


// 考题

function fun(n, o) {
  console.log(o);
  return {
    fun: function (m) {
      return fun(m, n);
    }
  }
}

// let a = fun(0); // undefined
// a.fun(1); // 0
// a.fun(2); // 0
// a.fun(3); // 0

// let b = fun(0) // undefined
//   .fun(1) // 0
//   .fun(2) // 1
//   .fun(3); // 2

let c = fun(0).fun(1); // undefined -> 0;
c.fun(2); // 1
c.fun(3); // 1
